package gbench.sandbox.neo4j.graph;

import static gbench.common.matlib.MatlibCanvas.println;
import static gbench.common.tree.LittleTree.MFT;
import static gbench.common.tree.LittleTree.IRecord.REC;
import static gbench.common.tree.LittleTree.KVPair.KVP;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.function.*;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.parser.PdfTextExtractor;

import gbench.common.fs.FileSystem;
import gbench.common.matlib.MatlibCanvas.XVec;
import gbench.common.tree.LittleTree;
import gbench.common.tree.LittleTree.IRecord;
import gbench.common.tree.LittleTree.Json;
import gbench.common.tree.LittleTree.Term;
import gbench.common.tree.LittleTree.TrieNode;
import gbench.common.tree.LittleTree.Jdbc.JdbcConfig;
import gbench.common.tree.LittleTree.Jdbc.JdbcExecute;
import gbench.common.tree.LittleTree.Jdbc.JdbcQuery;
import gbench.commonApp.jdbc.ISqlDatabase;
import gbench.commonApp.jdbc.Neo4jApp;
import gbench.commonApp.jdbc.Neo4jApp.Graph;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;



/**
 * 
 * @author gbench
 *
 */
public class JunitPathways {
    
    /**
     * neo4j 数据库操作接口<br>
     * Neo4jDatabase
     * 
     * @author gbench
     *
     */
    @JdbcConfig(url = "jdbc:neo4j:bolt://localhost/mydb3?nossl", driver = "org.neo4j.jdbc.Driver", user = "neo4j", password = "123456")
    interface Neo4jDatabase extends ISqlDatabase { // 数据接口
        @JdbcQuery
        List<IRecord> actIn(String name);

        @JdbcQuery
        List<IRecord> summary();

        @JdbcExecute
        void purchase(long transcode, String customer, String cashier, String product, String shop, double quantity,
                String unit, double price, Date createtime);
    }
    
    /**
     * 创建 Neo4jApp 对象
     * 
     * @return Neo4jApp
     */
    public Neo4jApp neo4jApp() {
        return Neo4jApp.newInstance("", null, null).set_intialize_handler(g -> {// 把交易代码也写入到边明路径中。
            final var edg_renderer = g.getEdge_name_renderer();// 记录老的边名记录器
            g.setEdge_name_renderer(edge -> edg_renderer.apply(edge) + "_" + edge.attr("code", 0));// 在边实体记录中增加transcode
            if (true)
                g.setAttr_ops((attributeSets, rec) -> {
                    final var key = rec.str("key"); // 边名/顶点名
                    final var name = rec.str("name"); // 属性名
                    final var value = rec.get("value", e -> (e instanceof IRecord)
                            ? ((IRecord) e).json().replace("\"", "\\\"")
                            : e != null && !e.getClass().isPrimitive() && !Number.class.isAssignableFrom(e.getClass()) && !(e instanceof String)
                                    ? LittleTree.trycatch(obj -> Json.obj2json(obj).replace("\"", "\\\"")).apply(e)
                                    : e);// 属性值
                    final var as = attributeSets.compute(key, (partB, v) -> (v == null ? REC() : v));
                    as.compute(name, v -> {
                        if (v == null) {
                            return value;
                        } else {
                            final var ll = as.pathL(name);
                            if (!ll.contains(value))
                                ll.add(value);
                            return ll.size() > 1 ? ll : ll.get(0);
                        }
                    });
                });
        }, false);// 注释intialize_handler 采用增量覆盖的方式 即replace 参数使用false
    }
    
    /**
     * 
     * @param filepath
     */
    public Graph graphOf(final String filepath,final String delim) {
        System.setProperty("user.timezone", "Asia/Shanghai"); // 设置时间格式
        
        final var ai = new AtomicInteger(0);// 边的序号
        final var pattern = Pattern.compile("\\s*([^\\{]+)(\\{[^\\}]+\\})\\s*"); // name {key:value} 的模式
        final var a_pre_lines = new AtomicReference<String>();
        final Stream<String> lines = FileSystem.utf8lines(new File(filepath)).map(String::strip).map(e->{
            final var line = e.replaceAll("[\\\\]*$", ""); // 去除尾部的 续行符号
            if (e.endsWith("\\")) {
                return a_pre_lines.updateAndGet(s->{
                    return s== null?line:s+""+line;
                });
            }else {
                final var pre_lines = a_pre_lines.get();
                if(pre_lines==null) {
                    return line;
                }else {
                    a_pre_lines.set(null);
                    return pre_lines+" "+line;
                }
            }
        })
        .peek(e->println(e));
        
        final var final_delim = delim != null ? delim : "[,\\s]+"; // 分隔符
        final var eis = lines.flatMap(line -> {
            final var splits = line.split("-+>"); // 分解路径模式
            final var path = splits[0].strip();// 第一部分路径信息
            final var names = (splits.length > 1 ? splits[1] : "REL").strip(); // 第二部分为属性信息
            return Arrays.stream(names.split("\\|")) // 分隔多边
                    .flatMap(e -> {
                        final var matcher = pattern.matcher(e);
                        var name = MFT("`{0}`", e);// 边的名称
                        var param = REC();// 边的数据参数
                        if (matcher.matches()) {// 边属性定义的模式
                            name = MFT("`{0}`", matcher.group(1)); // 获取边的名称
                            final var jsn = matcher.group(2).replaceAll("([\\w_][^\\\"]+):", "\\\"$1\\\":");// 对于:前面的字符字符串自动添加引号
                            param.add(Json.json2rec(jsn));
                        }
                        final var attr = REC("0#vlblk", "PartA", "1#vlblk", "PartB", "elblk", name,"code",ai.getAndIncrement(),"name", name).add(param); // 边的属性数据
                        final var pp = Arrays.stream(path.split("[\\]]+")) // 路径使用[] 进行分隔,路径的示例是诸如[a][b,c][d,e,f]这样的形式
                                .map(o->o.strip()).map(p->p.startsWith("[") && !p.endsWith("]")?(p+"]"):p)
                                .collect(Collectors.toList()); // 二元路径分解,pp[0] 前驱, pp[1] 后继
                        return IRecord.slidingS(pp, 2, 1, true).map(ss ->{ // 生成二元路径&属性
                                final var kvp = KVP(MFT(ss[0].endsWith("]") || ss[1].startsWith("[") ? "{0}{1}" : "{0}/{1}", ss[0], ss[1]), attr); // (二元路径,路径属性) 的键值对儿
                                //println("二元路径&属性",kvp);
                                return kvp;// 返回 二元路径&属性
                        });// ss map 
                    });// names flatMap
        }).collect(Neo4jApp.eisclc(final_delim)); // eis utf8lines flatMap
        final var g = neo4jApp().graph(eis);
        return g;
    }
    
    /**
     * neo4j 的使用演示
     */
    @Test
    public void foo() {
        final var filepath = FileSystem.path("pathways/steriod-hormone-action.sql", this.getClass());
        final var g = graphOf(filepath,";");
      //println(g);
        println("---------------------------------------------------------------------------------");
        g.getEdges().forEach(e->{
            println(e);
        });
    }
    
    /**
     * neo4j 的使用演示
     */
    @Test
    public void bar() {
        final var filepath = FileSystem.path("pathways/immunocytes-crosstalk-signaling-pathway.sql", this.getClass());
        final var g = graphOf(filepath,";");
        //println(g);
        g.getEdges().forEach(e->{
            println(e);
        });
    }
    
    /**
     * neo4j 的使用演示
     */
    @Test
    public void pharm() {
        final var filepath = FileSystem.path("pathways/pharms.sql", this.getClass());
        final var g = graphOf(filepath,",");
        // println(g);
        g.getEdges().forEach(e->{
            println(e);
        });
    }
    
    public URL url(String _url) {

        URL url = null;
        try {
            return new URL(_url);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        return url;
    }
    
    /**
     * 
     * @param url
     */
    public static void readPdf(URL url, Integer start, Integer end) {
        final var lines = new LinkedList<String>();
        try {
            PdfReader reader = new PdfReader(url);
            int pageNum = reader.getNumberOfPages();
            for (int i = start == null ? 1 : start.intValue(); i <= (end == null ? pageNum : end.intValue()); i++) {
                final var line = PdfTextExtractor.getTextFromPage(reader, i); // 读取第i页的文档内容
                lines.add(line);
            }
            lines.forEach(e -> {
                println(e);
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    @Test
    public void read() throws IOException {
        var path = ("D:/sliced/ebook/BiochemFFA_2_4.pdf");
        path = ("D:/sliced/ebook/Principles of Pharmacology.pdf");
        path = ("file:///D:/sliced/ebook/goals_objectives2.pdf");
       
        readPdf(url(path),null,null);
        
    }
    
    /**
     * yml 的格式解析
     * 
     * @param file        文件路径
     * @param start_level 开始层级，从0开始
     * @return TrieNode 根节点的值 为 null
     */
    public TrieNode<String> yml(final String file) {
        final var path = FileSystem.fileOf(file, this.getClass()); // 文件路径
        final BiFunction<String,String,String> merge = (a,b)->Term.FT("$0,$1", a,b.strip());
        final Function<AtomicReference<String>, Function<String, String>> continue_line = a_pre_lines -> e -> {
            final var line = e.replaceAll("[\\\\]*$", ""); // 去除尾部的 续行 符号 \
            if (e.endsWith("\\")) { // 包含有 下一行
                a_pre_lines.updateAndGet(s -> { // 设置当前行
                    return s == null ? line : merge.apply(s, line);
                });
                return null; // 返回空值 用以表名 这是一个未完成的行，需要后面的 处理函数 给与过滤掉 这一null行。
            } else { // 没有续行符
                final var pre_lines = a_pre_lines.get(); // 获取前一行数据
                if (pre_lines == null) { // 没有前导行
                    return line;
                } else { // 包含前导行
                    a_pre_lines.set(null); // 清空前一行数据
                    return merge.apply(pre_lines, line);
                } // if
            } // if
        }; // 续行函数 continue_line
        final var lines = FileSystem.utf8lines(path)
            .map(continue_line.apply(new AtomicReference<String>())) // 续航计算
            .filter(e->e!=null && !e.matches("\\s*")) // 去除空行 和 null 值
            .collect(Collectors.toList());
        final var n = lines.size(); // 行数目
        final var ident_pattern = Pattern.compile("^\\s*"); // 缩进的pattern
        final var ident_entries = new TreeMap<Integer, String>(); // TreeMap 键值排序，层级结构
        final var trie = new TrieNode<String>(null,LinkedHashMap::new); // Trie 根结构
        
        for (int i = 0; i < n; i++) { // 逐行处理
            final var line = lines.get(i);
            final var matcher = ident_pattern.matcher(line); //  缩进侦测
            if (matcher.find()) { // 满足缩进格式
                final var ident = matcher.group(); // 获取缩进距离
                final var level = ident.length() / 4; // 当前数据项目的的层级深度, 每个tab 键名 采用 4个 空格进行划分
                ident_entries.put(level, line.strip()); // 记录键值的层级
                if (ident.length() % 4 != 0) { // 提取非格式化部分
                    System.err.println(Term.FT("error:$0\t$1\t$2", i + 1, level, line));
                } else {
                    final var flds = new LinkedList<String>(); // 字段列表
                    ident_entries.entrySet() // 获取行项目
                        .stream() // 层级的排序 按照 层级深度进行排序, 层级越深 表示 数据越细节
                        .filter(e -> e.getKey() <= level) // 提取 当前层级 以上的所有 摘要层级
                        .forEach(e -> { // 层级深度
                            flds.add(e.getValue());
                        }); // 构造字段集合 
                   trie.addParts(flds).flag(false);
                } // if
            } else {
                System.err.println(line);
            } // if
        } // for
        
        return trie;
    }
    
    /**
     * yml 的格式解析
     * 
     * @param file        文件路径
     * @param start_level 开始层级，从0开始
     * @return 行列表
     */
    public Stream<XVec<String>> yml(final String file, final int start_level) {
        
        return yml(file).stream(e->XVec.OF(e.parts()).cdrX())
                .filter(e->e.size()>=start_level+1);
    }

    /**
     * 层级深度的数据结构化
     */
    @Test
    public void drugs(){
        final var path = FileSystem.path("pathways/drugs.txt", this.getClass());
        
        println("--------------------------------------------");
        yml(path,3).forEach(e->{
            println(e);
        });
    }
    
    /**
     * 层级深度的数据结构化
     */
    @Test
    public void embrology(){
        final var path = FileSystem.path("pathways/medical_embrology.txt", this.getClass());
        
        println("--------------------------------------------");
        yml(path,0).forEach(e->{
            println(e);
        });
    }
    
    /**
     * 层级深度的数据结构化
     */
    @Test
    public void guiguzi(){
        final var path = FileSystem.path("D:\\sliced\\ws\\gitws\\janus\\src\\main\\resources\\docs\\guiguzi\\guiguzi.txt", this.getClass());
        
        println("--------------------------------------------");
        final var root = yml(path); // 生成文档根节点
        root.forEach(e->{
            println("\t".repeat(e.getLevel())+e.getPathCode()+"\t"+e.getValue(),e.partS().toArray());
        });
        
        // 根节点
        println("根节点",root.path());
        println("root.pathOf(null,\"鬼谷子的思想\",\"捭阖\")",root.pathOf(null,"鬼谷子的思想","捭阖").token("/"));
        // child
        println("root.childAt(0,0,0,1)",root.childAt(0,0,0,1));
        println("root.childOf(\"鬼谷子的思想/捭阖\".split(\"/\"))",root.childOf("鬼谷子的思想/捭阖".split("/")).token("/"));
        
    }

}
